WICSコンパイラ ――――――――――――――――――――――――――――――――――――――― I/O 1981年 11月号掲載 MZ−80 [Source] WICSプログラム マシン語 0A000H−0BFFFH 起動方法 WICSを起動してからロード ――――――――――――――――――――――――――――――――――――――― 今月号では、WICSコンパイラの全リストを公開します。 WICSコンパイラはやはりWICS自身で書かれており、 かなりのレベルまで最適化を行なうので、 従来ならアセンブラでないと不可能であった仕事も、 コンパイラが代役を果たすことができるようになりました。   ・−−−−−−−−−−−−−−・  /    コンパイラの概要    \ ・−−−−−−−−−−−−−−−−−−・ 本コンパイラは、WICSのソース・プログラムを読み込んで Z80アブソリュート・オブジェクト・コードを直接、生成するものです。 『WICSソース・プログラムを読み込む』と言っても、 実際はメモリ上にあるソース・プログラムを 配列ポインタによって読ませているだけです。 コンパイラの大きさはソース・リストで 約14Kバイト(600行ほど)、 オブツェクトになった場合は約7Kバイトの小さなものです。 先ほど、このコンパイラはWICSで記述されている、 と言いましたが、いくらインタープリタを速くしても、 複雑なコンパイル作業をインタープリタにより行なうのは 非常に時間がかかるので、 一度、自分(コンパイラのソース)自身を インタープリタ・モードでコンパイルして、 Z80のオブジェクトにしてしまいます。 そうすると、コンパイル作業を非常に高速化することができます。 このように(アセンブラでなく)コンパイラで、 コンパイラを作成することを『コンパイラ・コンパイラ』 と呼んでいます(非常に活かややこしい)。 できあかったコンパイラのオブジェクト(以後、単にコンパイラと言う)は 自分自身(すなわち、600行にわたるコンパイラ・ソース)を すべてコンパイルするのにわずか30秒しかかかりません (2MHz Z80の場合)。 なお、コンパイラしたオブジェクトは、 WICSランタイム・パッケージに依存して走ります。 また、現在のバージョンではランタイム内にワーク・エリアがあり ROM化できませんが コンパイル・オブジェクトはROM化可能であり (ただし 変数分離の必要がある)、 PRINT文のメッセージ以外は 通常のZ80マシン・コードなので 逆アセンブルやリロケータにかけることもできます。    ・−−−−−−−−−−−−・   / コンパイラの行なう    \  /  最適化について       \ ・−−−−−−−−−−−−−−−−−−・ たとえばアセンブリ言語を使ってプログラムを作る場合、 できるだけプログラムを短かく、 かつ速くしようと努力するのが普通です (プログラムは、通常、短かくすればそれだけ速くなります。  もちろん例外もありますが)。 たとえば、ポインタを1つ先へ進める場合、 「加算」を行なわずにインクリメント命令を置きます。 あるいは、レジスタを2倍にする場合、 「乗算」を行なわず、自分に自分を加える加算命令を置きます。 いずれも後者の方が、よりコンパクトで、しかも速いからです。 上に述べたような最適化は、 ローカルな(局所的)最適化と呼ばれているものです。 これに対してグローバルな(大局的)最適化は次のようなものです。 ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ | 10 FOR I=1 T0 100              | | 20 J=10:K=20                   | | 30 A=J*K+I                     | | 40 NEXT                        | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ このプログラムではFOR〜NEXTループの中に、 J、Kという変化しない変数があり、 また、いつも同じ答になるJ*Kを 毎回行なうことになっています。 そこでグローバルな最適化を行なうコンパイラは J、Kの代入文とJ*Kをループの外へ出し、 次のように変形してしまいます。 ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ |    J=10:K=20                   | |    t=J*K    (tはコンパイラが作り出した変数)  | | 10 FOR I=1 T0 100              | | 30 A=t+I                       | | 40 NEXT                        | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ つまり、グローバルな最適化はプログラムの構造自体変えてしまいます。 このことは、最適化が副作用をもたらす危険性をはらんでいます。 どういうことかというと、ループ内にある関数が、 たとえばSIN(0.1)のようなものであれば、 ループ外に出すのは問題となることはないのですが、 PEEK($E000)のようなものは、引用するたびに 変わる危険があるからです。 実際上、グローバルな最適化というのは、 大型機の場合でも、行なうかどうかを ユーザーが指定するようになっているものもあるほど、 慎重にやらなければいけないのです。 あるいは、ソース・プログラムの段階で、 人間が行なった方が、より好ましいと言えるでしょう。 WICSコンパイラでは、グローバルな最適化は、 アルゴリスムが複雑になるため、行なっていません。 ですからソース・プログラムで一見してムダとわかるような プログラムは避けてください。 一方、ローカルな最適化はかなりのレベルまで行なっています。 こればかりは、いくらソース・プログラムをムダなく作っても関係ないので、 コンパイラ側か行なわなければなりません。    ・−−−−−−−−−−−−・   / WICSでおこなって   \  /  いる最適化について     \ ・−−−−−−−−−−−−−−−−−−・ プログラマーは、コンパイラがどのように 最適化を行なうかについて知識をもっていれば、 より効率の良いプログラムを作ることが可能になります。 そこで、WICSコンパイラの最適化機構について一応説明します。 最適化機構は大きく分けて、  (1)式の展開時の最適化  (2)直前に参照された変数の参照省略  (3)IF文中の判断の最適化  (4)相対分岐化 の4つがあります。  (1)は、一般的に、式の展開を最適に行なう手法です。  たとえば、A=B+1を展開してみます。 ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ | リスト1                                | | LD   HL,(VAR.B)−・;VAR.Bは変数Bの格納アドレス。 | | PUSH HL         |DEレジスタに右辺第2項の評価値を  | | LD   HL,1       |入れる。ただし,HLレジスタを壊し  | | EX   DE,HL     −・てはいけない。            | | POP  HL                             | | ADD  HL,DE     ;加算                  | | LD  (VAR.A),HL ;Aに代入                | |−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−| | リスト2                                | | LD  HL,(VAR.B)                      | | LD  DE,1                            | | ADD HL,DE                           | | LD (VAR.A),HL                       | |−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−| | リスト3                                | | LD  HL,(VAR.B)                      | | INC HL                              | | LD  (VAR.A),HL                      | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ リスト1は、一般的な手法で、“+”という2項演算子の左項を HLレジスタに、右項をDEレジスタにそれぞれ評価値を入れ、 加算して、変数Aに代入する操作をそのまま展開したものです。 ところが、右項(1)の評価を行なうのに、 HLレジスタを使わないでやる (DEレジスタにそのまま1を入れる)方法が存在するので、 PUSH、POPではさまれた部分を別の命令に置き換えて、 リスト2のようになります (ここで、右項が、乗算を含む複数の項であったりすると、  リスト2に置き換えることはできません)。 さらに、±3までの定数を加える(あるいは引く)操作は、 しかるべきインクリメント(あるいはデクリメント)命令に置き換えて (リスト3)プログラムの効率を上げます (リスト3が最終的なオブジェクトです)。 (2)はどういうことかというと ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ |  10 A=A+1:B=A                  | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ のようなプログラムは、 ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ | リスト4                           | | LD HL,(VAR.A)                  | | INC HL                         | | LD (VAR.A),HL                  | | ;                              | | LD HL,(VAR.A);このLD命令はムダです。     | | LD (VAR.B),HL                  | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ のように展開されますが、 4行目のLD命令は、その一つ前の命令が HLの内容が(VAR.A)に等しいことを保障しているので、 無駄になっています。 したがってWICSコンパイラは4行目のLDを省略します。 しかし、 ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ | 10 A=A+1                       | | 20 B=A                         | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ と、2行に分かれている場合は話が別です(省略しません)。 (3)IF文中の最適化。 IF文では、条件式を、一般的な式評価と同じ方法で評価します。 その評価値が“0”ならば“偽”、“0”以外ならば“真”とみなして THEN以下を実行するのです。 したがって、「=」、「<>」、「>」、「<」などの関係演算子は “0”(偽)が“1”(真)のいずれかの値をとるようになっており、 HLとDEを比較して“0”か“1”を HLレジスタに返すサブルーチンが呼ばれる構造になっています。 条件式がANDやORで複雑化している場合は そのままにしかなりませんが、 単純な場合(IF A=0など)は次の例のように最適化します。 (4)の相対分岐化は、 分岐命令が−128〜+127バイトでとどく範間内に分岐する命令を見つけて、 自動的に柏対分岐命令に置き換えるものです。 そうでないものは1バイト多い絶対アドレス分岐命令になります。 絶対アドレス分岐はCPUサイクルで10クロック、 相対分岐は条件成立で12クロック、不成立で7クロック (1クロックは2MHz Z80で0.5μ秒に当る)要し、 相対化を行なってもほとんど速くなることはないのですが、 プログラム・サイズを縮めることができます。 ただし、そのためにPASSが2つ増えて4PASSになってしまうので、 コンパイルする前にコンパイラは、 オペレータに2PASSか4PASSかを尋ねることにしました。 当然ながら2PASSのときは相対化は行ないません。 そのほかでは、FOR〜NEXT文で、STEPを省略した場合、 制御変数に増分1を加えるところがインクリメントになったり、 またマイナスの付いた定数は負のサブルーチンを呼ばず コンパイルする時点で負に変換したり、 あるいは定数を引く代わりに、 定数を2の補数にして加算命令に置き替えるなど、 細かい点をあげるときりがありません。 ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ | 例1                              | |    10 IF A=0 GOTO 10            | | L10: LD HL,(VAR.A)              | |      LD A,L                     | |      OR H                       | |      JR Z,L10                   | | 例2                              | |   20 IF B=”*” RETURN            | | L20: LD HL,(VAR.B)              | |      LD A,L                     | |      SUB A,$2A($2Aは”*”のASCIIコード)| |      OR H                       | |      RET Z                      | | 例3                              | |   30 IF S[0]<>0 RETURN          | | L30: LD HL,(VAR.S)              | |      LD A,(HL)                  | |      OR  A                      | |      RET NZ                     | | 注)条件式直後にGOTOあるいはRETURNを書くと      | |   Z80マシン語のJP(あるいはJR)、           | |   RET命令に変換されます。                 | |   ところが、THENを入れると、               | |   条件式不成立時に次の行ヘジャンプする分岐命令が       | |   必ず生成され、そのあとでTHEN以降が通常どおり      | |   展開されます。                       | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・  表1 WICSメモリ・マップ(MZ−80K/C版) ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ | 0000・―――――――――・ | |  |#### ROM #########| | | |#### SP-1002 #####| ・−$1200 インタプリタ コールド・スタート | | 1000|―――――――――|−| | | 1200|―――――――――| ・−$1203 インタプリタ ホット・スタート | | |WICSランタイム・パッケージ:| $1206 ランタイム・モニタ | | 1E00|―――――――――| | | |::::::::::::::::::| | | |:::WICSインタープリタ:::| | | |::::::::::::::::::| | | 3400|―――――――――| | | |インタープリタ用ワーク・エリア | | | 4000|―――――――――|●$1200〜$1DFFまでのランタイム・ | | | | パッケージは必ず必要です。 | | | |●コンパイラ(オブジェクト)のワーク・エリア | | |↓ユーザー用 | として$400〜$4000までを | | | テキスト,オブジェクト | 使うことができる(なぜな | | | ワーク・エリア | らコンパイル中はインタープリタ | | 8000| | は不要)。 | | | |●ユーザープログラムを$1E00〜 | | | | から作成すればコンパクトな独立 | | | | オブジェクト(ランタイムを含ませる) | | A000|―――――――――| 作成が可能です。 | | |::::WICSコンパイラ::::| その場合、オート・スタートは$1E00でなく、 | | |::::(オブジェクト)::::| $1200とした方が良い | | C000|―――――――――| (IYレジスタを初期化して$1E00へ | | | ワーク・エリアなど | 跳ぶようになっている)。 | | D000|―――――――――| | | | VIDEO RAM | | | E000|―――――――――| | | | | | | | I/O | | | FFFF・―――――――――・ | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・    ・−−−−−−−−−−−−・   / WICSコンパイラを   \  /  走らせるにあたって     \ ・−−−−−−−−−−−−−−−−−−・ まず、先月号で発表したWICSインタープリタおよび ランタイム・パッケージを完全にデバッグしてください。 そして今回発表すつリストをインタープリタのエディタ で打ち込んでくいださい。 一応、念のために言っておきますが、 今回発表するコンパイラ・リストは MZ−80CまたはMZ−80K2(メモリは48KBあることが望ましい)の WICSインタープリタ上で動作するものです。 間違ってもPC−8001や、シャープBASIC上で 打ち込まないでください。 無駄な努力になります。 月並みですが、打ち込んだら一応テープにセーブしておきます。 さて、走らせ方は簡単です。「RUN」と打つと ソース・プログラム番地、オブジェクト・プログラム番地 (これがわからない人はいないでしょうね)、 次にワーク・エリアを尋ねてきます。 ワーク・エリアはコンパイル作業時に各種テーブル作成領域として必要ですが コンパイルが終了すれば不要になります。 目安としてソース10KBに対し2KB程の大きさが必要と思ってください。 要するに空きエリアを適当に($C000など)指定してやってください。 次に、アドレス表を表示するかどうか尋ねてくるので、YかNで答えます。 さらに、PASSを2PASSにするか4PASSにするかを尋ねてきます。 これも2か4のどちらかを入力します。 ここまで入力すればあとはコンパイラがやってくれます。 コンパイルが無車終了すれば「COMPILE OK!」と表示され、 オブジェクトの大きさと番地、変数表が順に表示されコンパイルを終了します。 ソース・プログラムにエラーがあると、 しかるべきエラーメッセージを出してコンパイルを中止します。 ただし、コンパイラの打ち込みミスでバグがあると、 それ以外の異常動作が起こるかもしれません。 操作の一例としてコンパイラ自身を $A000番地に生成させる例をあげておきます。 整数型とは言うものの、やはり高級言語は扱いやすいですね。 何といってもメンテナンスや変更がアセンブラに比べ非常に楽です。 現在、多くのマイコン内蔵制御機器のプログラム開発は 大半がアセンブリ言語を使っていると聞きますが、 プログラムがたまってくるとこんどは保守や仕様変更などで 開発と同じぐらい手間がかかります。 したがって、何らかの高級言語を使っていれば、 プログラムの保守に要する時間はかなり減らすことができるでしょう。 もちろん、開発も楽になります。 私たちはBASICと文法的にコンパチブルな 整数型コンパイラを開発することで 問題解決を図ろうとしたわけです。 もちろん、制御と一口でいっても多種多様ですから、 たとえばアセンブラ数10ステップで事足りればそれでよいし、 BASICインタープリタで間に合う低速処理ならば BASICでよいと思います。 しかし、アセンブラの高速性と複雑な処理という 2条件を満たす必要のある場合、 このコンパイラは力を発揮するでしょう。 いまのところ、このコンパイラはゲーム・プログラムの開発と、 そのほか3和音自動演奏プログラムなどの開発などに使っていますが、 現時点でも制御用マイコンにランタイムごとROM化するのは、 多少、システムを書き換えるだけで可能です。 今後の課題としては、割り込みを記述できるようにすることと、 限定的な形で実数を扱かえるようにすることです。 表2 コンパイラのエラー・メッセージ ・−−−−−−−−−−−−−−−−−−−−−−−−−−−・ | メッセージ |      意     味      | |−−−−−−−+−−−−−−−−−−−−−−−−−−−| |SYNTAX ERROR |文法上の誤り。 | |−−−−−−−+−−−−−−−−−−−−−−−−−−−| |UNDEF LINE NO.|GOTO,GOSUBの飛び先に当る行がない。 | |−−−−−−−+−−−−−−−−−−−−−−−−−−−| |TOO LONG,.THEN|THEN以降が長すぎる(128バイトを超えた)。 | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−・ 表3 コンパイラの構造 ・−−−−−−−−−−−−−−−−−−−−−−−−−−−・ | 行番号 |       意      味       | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| |100-490 | メイン・ルーチン             | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 500 | スペース読み飛ばし            | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 540 | ランタイム・ライブラリ・コール生成    | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 570 | 単なるコール命令命生成          | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 600 | 中間コードかどうかチェック        | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 640 | 飛び先行番号を捜がす。          | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 700 | 式の最適化                | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 800 | 変数をHLにロードする          | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 840 | HLを変数にストアする          | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 1000 | 1パス処理                | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 1140 | 1行処理                 | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 1190 | 1ステップ処理              | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 1230 | 代入文                  | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 2000 | ステートメントの各処理          | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 5000 | 式評価一HLレジスタ           | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 6000 | 項評価→HLレジスタ           | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 7000 | 関数                   | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 8000 | 変数アドレス管理             | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 10000 | テーブル表示               | |−−−−+−−−−−−−−−−−−−−−−−−−−−−| | 14000 | エラー・メッセージ            | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−・  表4 コンパイラで使用した主な変数 ・−−−−−−−−−−−−−−−−−−−−−−−−−−−・ |変数名|        意     味        | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |SRC |ソース・プログラム先頭アドレス        | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |OBJ |オブジェクト・プログラム先頭アドレス     | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |WKG |ワーク・エリア・アドレス           | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |DSPSW |テーブル表示を行なう→1           | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |DSW |2PASS→1、4PASS→0        | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |LIB |ライブラリ、先頭アドレス           | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |ERN |エラーの種類                 | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |PASS |パスの回数1〜2または1〜4まで変化する   | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |S |ソース・プログラム・ポインタ         | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |B |オブジェクト生成ポインタ           | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |OPT |最適化により、定数をロードしたかどうかのフラグ| |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |FSTK |FOR〜NEXT生成スタック         | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |TABLE |行番号対オブジェクト番地ルックアップ・テーブル| |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |VTBL |変数名管理テーブル              | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |OLDLET|最後に行なわれた代入命令のアドレス      | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |OLDVAR|最後に行なわれた代入命令が扱かった変数名   | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |CAL |CALL命令のオベランド           | |−−−+−−−−−−−−−−−−−−−−−−−−−−−| |VA |変数の実アドレス               | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−・ コンパイラ自身をコンパイルした例 ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・ |--- WICS COMPILER VER 1.2 --- | | | |SOURCE PROGRAM ADRS:$4000 | |OBJECT PROGRAM ADRS:$A000 | |WORKING AREA ADRS:$C000 | |ADDRESS TABLE(Y/N) N | |PASS (2/4) 4 | |>PASS - 1 | | | |>PASS - 2 | | | |>PASS - 3 | | | |>PASS - 4 | | | | --- COMPLETE ! --- | |OBJECT SIZE : 7244 ($A000-$BC4B) | | | | --- VARlABLES --- | |BC4C:SRC BC4E:PR4 BC50:OBJ BC52:WKG | |BC54:DSPSW BC56:PSW BC58:LIB BC5A:ERN | |BC5C:PASS BC5E:A BC60:S BC62:CAL | |BC64:B BC66:JA BC68:T2 BC6A:TABLE | |BC6C:HL BC6E:T BC70:OPT BC72:B1 | |BC74:OLDLET BC76:OLDVAR BC78:VA BC7A:FSTK | |BC7C:VTBL BC7E:VTBLEND BC80:LNO BC82:BEND | |BC84:IXB BCB6:ATDAT BC88:OPI BC8A:JIF | |BC8C:IXF BC8E:OP BC90:B2 BC92:SR | |BC94:RELFLG BC96:BM BC98:S1 BC9A:V1 | |BC9C:VNO BC9E:MATCH BCA0:VF | | | ・−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−・